#自编码网络可以看做将数据进行压缩（由原来“n-维”压缩成“m-维”(m=隐藏层神经元数目))，
#然后再在需要的时候用损失尽量小的方式将数据恢复出来

import numpy as np
import sklearn.preprocessing as prep
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

#参数初始化方法xavier initialization,会根据某一层网络的输入、输出节点数量自动调整最合适的分布
#Xaiver初始化器做的事情就是让权重被初始化得不大不小，正好合适。
#从数学的角度分析,Xavier就是让权重满足0均值，同时方差为2/nin+nout,分布可以用均匀分布或者高斯分布

#功能说明：通过tf.random_uniform创建一个范围内的均匀分布,
#输入： fan_in输入节点数量， fan_out输也节点数量
def xaier_init(fan_in, fan_out, constant=1):
    low = -constant * np.sqrt(6.0 / (fan_in + fan_out))
    high = constant * np.sqrt(6.0 / (fan_in + fan_out))

    return tf.random_uniform((fan_in, fan_out),
                             minval = low, maxval = high,
                             dtype = tf.float32)

#定义一个去噪自编码的class,这个类会包含一个构建函数__init__(),
class AdditiveGaussianNoiseAutoencoder(object):
    def __init__(self, n_input, n_hidden, transfer_function = tf.nn.softplus, optimizer = tf.train.AdamOptimizer(), scale=0.1):
        self.n_input = n_input
        self.n_hidden = n_hidden
        self.transfer = transfer_function
        self.scale = tf.placeholder(tf.float32)
        self.training_scale = scale

        network_wrights = self._initialize_weights()

#功能说明，先创建一个名为all_weights的字典dic,然后将w1,b1,w2,b2全部存入其中，最后返回all_wrights
#1、其中w1需要使用前面定义的xavier_init函数初始化，我们直接传入输入节点数和隐含层节点数，然后xavier
#即可返回一个比较适合于softplus等激活函数的权重初始分布，
#2、而偏置b1只需要使用tf.zeros全部置0即可
#3、对于输出层self.recostruction，因为没有使用激活函数，这里将w2、b2全部初始化为0即可
def _initialize_weights(self):
    all_weights = dict()
    all_weights['w1'] = tf.Variable(xavier_init(self.n_input,self.n_hidden))
    all_weights['b1'] = tf.Variable(tf.zeros([self.n_hidden], dtype = tf.float32))
    all_weights['w2'] = tf.Variable(tf.zeros([self.n_hidden,self.n_input], dtype=tf.float32))
    all_weights['b2'] = tf.Variable(tf.zeros([self.n_input], dtype=tf.float32))
    return all_weights

#计算损失cost及执行一步训练的函数，用一个batch数据进行训练并返回当前的损失cost
#函数里只需让Session执行两个计算图的节点，这两个计算节点分别是损失cost训练过程optimizer,
#输入：feed_dict包括输入数据X，以及噪声系数scale
def partial_fit(self,X):
    cost, opt = self.sess.run((self.cost, self.optimizer),
                              feed_dict = {self.x: X, self.scale: self.training_scale})
    return cost

#只计算损失cost的函数calc_total_cost,这里就只让Session执行一个计算图节点self.cost,
#这个函数是在自编码器训练完毕后，在测试集上对模型性能进行评测时会用到的，它不会像partial_fit那样触发训练操作
def calc_total_cost(self,X):
    return  self.sess.run(self.cost, feed_dict = {self.x: X, self.scale: self.training_scale})

#返回自编码器隐含层的输出结果，它的目的是提供一个接口来获取抽象后的特征，自编码器隐含层的最主要功能就是学习出数据中的高阶特征
def transform(self,X):
    return  self.sess.run(self.hidden, feed_dict = {self.x: X, self.scale: self.training_scale})

#功能说明：它将隐含层的输出结果作为输入，通过之后的重建层将提取到的高阶特征复原为原始数据
#          这个接口和前面的transform正好将整个自编码器拆分为两部分，这里的generate接口是后半部分，
#          将高阶特征复原为原始数据的步骤
def generate(self, hidden = None):
    if hidden is None:
        hidden = np.random.normal(size = self.weighrs["b1"])
    return self.sess.run(self.reconstruction, feed_dict = {self.hidden: hidden})

#它整体运行一遍复原过程,包括提取高阶特征和通过高阶特征复原数据,即包括transform和generate两块.输入数据是原数据
#输出数据是复原后的数据
def reconstruct(self,X):
    return self.sess.run(self.reconstruction, feed_dict = {self.x: X, self.scale: self.training_scale})

#获取隐含层的权重w1
def getWeights(self):
    return self.sess.run(self.weights['w1'])

#获取隐含层的偏置系数b1
def getBiases(self):
    return self.sess.run(self.weights['b1'])


